package com.example.concurrent.chapter05;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;

import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.currentThread;

/**
 * @author Liuqz
 * @date 2018/11/21 23:13
 */
public class BooleanLock implements Lock {

    /**
     * 代表当前拥有锁的线程
     */
    private Thread currentThread;

    /**
     * false 代表当前该线程没有被任何线程获得或者已经释放
     * true  代表该锁已经被某个线程获得
     */
    private boolean locked = false;

    /**
     * 用来存储哪些线程在获取当前线程时进入了阻塞状态
     */
    private final List<Thread> blockedList = new ArrayList<>();


    @Override
    public void lock() throws InterruptedException {
        //1.使用代码同步块进行方法同步
        synchronized (this) {
            //2.如果当前锁已经被某个线程获取,则将该线程加入到阻塞队列中,并且使当前线程wait释放对this monitor的所有权
            while (locked) {
                //暂存当前线程
                final Thread tempThread = currentThread();

                try {
                    if (!blockedList.contains(currentThread())) {
                        blockedList.add(currentThread());
                    }
                    this.wait();
                } catch (InterruptedException e) {
                    //如果当前线程在wait时被中断,则从blockedList中将其删除,避免内存泄漏
                    blockedList.remove(tempThread);
                    //继续抛出中断异常
                    throw e;
                }
            }
            /**
             * 3.如果当前锁没有被其他线程获取,则该线程将尝试从阻塞队列中删除自己(注意:如果当前线程从未进入到阻塞队列中,删除方法并不会有任何影响; 如果当前线程是从wait set(线程休息室)中被唤醒的,则需要将自己从阻塞队列删除自己)
             */
            blockedList.remove(currentThread());
            //4.locked开关为true
            this.locked = true;
            //5.记录获取锁的线程
            this.currentThread = currentThread();
        }
    }

    @Override
    public void lock(long mills) throws InterruptedException, TimeoutException {
        synchronized (this) {
            //1.如果mills不合法,就抛出语句
            if (mills <= 0) {
                System.out.println("param is not right");
            } else {
                long remainingMills = mills;
                long endMills = currentTimeMillis() + remainingMills;
                while (locked) {
                    //2.如果remainingMills小于等于0,则意味着当前线程被其他线程唤醒或者在指定的wait时间到了之后还没有获得锁,这种情况下抛出TimeoutException
                    if (remainingMills <= 0) {
                        throw new TimeoutException("can not get the lock during " + mills);
                    }
                    if (!blockedList.contains(currentThread())) {
                        blockedList.add(currentThread());
                    }
                    //3.等待remainingMills的毫秒数,该值最开始是由其他线程传入的,但在多次wait的过程中会重新计算
                    this.wait(remainingMills);
                    //4.重新计算remainingMills时间
                    remainingMills = endMills - currentTimeMillis();
                }
                /**
                 * 5.获得该锁,并且从block列表中删除当前线程,将locked的状态改为true并且指定获取锁的线程就是当前线程
                 */
                blockedList.remove(currentThread());
                this.locked = true;
                this.currentThread = currentThread();
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this) {
            //1.判断当前线程是否是获取锁的那个线程,只有加了锁的线程才有资格进行解锁
            if (currentThread == currentThread()) {
                //2.将locked状态修改为false
                this.locked = false;

                Optional.of(currentThread().getName() + " release the lock").ifPresent(System.out::println);

                //3.通知其他在wait set的线程,你们可以再次尝试抢锁了,这里使用notify和notifyAll都行
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedList);
    }
}
